import * as bip39 from 'bip39';
import * as ecc from 'tiny-secp256k1';
import * as bitcoin from 'bitcoinjs-lib';
import { BIP32Factory, BIP32Interface } from "bip32";
import { computeAddress, ethers, parseEther } from "ethers";
import { regtestUtils } from "../utils/bitcoin";
import { Network, networks } from "bitcoinjs-lib";
import axios from "axios";
import * as dotenv from "dotenv";

dotenv.config();
const POLYGON_RPC_URL = process.env.POLYGON_RPC_URL;

export class HdWallet {

  bip32 = BIP32Factory(ecc);

  private static instance: HdWallet | null = null;
  private rootNode: BIP32Interface;
  private childNode: BIP32Interface;
  public network: string = "bitcoin_testnet";
  //public accountMap : Map<string,number>;  //key为network,value为该network下的账户的maxIndex

  private constructor() {}

  public static getInstance(): HdWallet {
    if (!HdWallet.instance) {
      HdWallet.instance = new HdWallet();
    }
    return HdWallet.instance;
  }

  public create(network: string,password: string){
    this.setNetwork(network);
    let mnemonic = bip39.generateMnemonic();
    this.generate(mnemonic,password);
    return mnemonic;
  }

  public recover(network: string, mnemonic: string,password: string){
    this.setNetwork(network);
    this.generate(mnemonic,password)
    return this;
  }

  private generate(mnemonic: string,password: string){
    let seed = bip39.mnemonicToSeedSync(mnemonic,password)
    this.createRootNode(seed)
    console.log("----------this.rootNode.privateKey=",this.rootNode.privateKey.toString("hex"))
  }

  private createRootNode(seed: Buffer){
    switch (this.network) {
      case "bitcoin":
        this.rootNode = this.bip32.fromSeed(seed,networks.bitcoin);
        break;
      case "bitcoin_testnet":
        this.rootNode = this.bip32.fromSeed(seed,networks.testnet);
        break;
      case "ethereum":
        this.rootNode = this.bip32.fromSeed(seed);
        break;
      case "arbitrum":
        this.rootNode = this.bip32.fromSeed(seed);
        break;
      case "polygon":
        this.rootNode = this.bip32.fromSeed(seed);
        break;
      default:
        throw new Error('Invalid network');
        break;
    }
  }

  private generateBitcoinAddress(publicKey: Buffer,network: Network): string {
    const { address } = bitcoin.payments.p2pkh({ pubkey: publicKey,network: network });
    return address;
  }

  private generateEthereumAddress(privateKey: string): string {
    return computeAddress('0x'+privateKey)
  }

  private getPrivateKey(): string{
    let privateKey1 = this.childNode.toWIF();
    let privateKey2 = this.childNode.privateKey.toString("hex");
    console.log("----------privateKey1=",privateKey1);
    console.log("----------privateKey2=",privateKey2);
    return this.childNode.privateKey.toString("hex");
  }

  public createAccount(index: number): { address: string, privateKey: string } {
    let address: string;
    let privateKey: string;
    switch (this.network) {
      case "bitcoin":
        this.childNode = this.rootNode.derivePath(`m/44'/0'/0'/0/${index}`);
        address = this.generateBitcoinAddress(this.childNode.publicKey,networks.bitcoin);
        break;
      case "bitcoin_testnet":
        this.childNode = this.rootNode.derivePath(`m/44'/1'/0'/0/${index}`);
        address = this.generateBitcoinAddress(this.childNode.publicKey,networks.testnet);
        break;
      case "ethereum":
        this.childNode = this.rootNode.derivePath(`m/44'/60'/0'/0/${index}`);
        address = this.generateEthereumAddress(this.getPrivateKey());
        break;
      case "polygon":
        this.childNode = this.rootNode.derivePath(`m/44'/966'/0'/0/${index}`);
        address = this.generateEthereumAddress(this.getPrivateKey())
        break;
      case "arbitrum":
        this.childNode = this.rootNode.derivePath(`m/44'/9001'/0'/0/${index}`);
        address = this.generateEthereumAddress(this.getPrivateKey())
        break;
      default:
        throw new Error('Invalid network');
        break;
    }
    return {
      address: address,
      privateKey: this.getPrivateKey(),
    };
  }

  // 切换比特币测试网和以太坊测试网
  public setNetwork(network: string): void {
    this.network = network;
  }

  private async createPayment() : Promise<any>{
    return (bitcoin.payments as any)['p2pkh']({
      pubkey: this.childNode.publicKey,
      network: bitcoin.networks.testnet
    });
  }

  private async getInputData(
    amount: number,
    payment: any
  ): Promise<any> {
    const unspent = await regtestUtils.faucetComplex(payment.output, amount);
    const utx = await regtestUtils.fetch(unspent.txId);
    const mixin = Buffer.from(utx.txHex, 'hex');
    return {
      hash: unspent.txId,
      index: unspent.vout,
      ...mixin,
    };
  }

  public async getBalance(): Promise<string>{
    let balance: string;
    switch (this.network) {
      case "bitcoin":
        break;
      case "bitcoin_testnet":

        break;
      case "ethereum":
        break;
      case "polygon":
        const provider = ethers.getDefaultProvider(POLYGON_RPC_URL);
        const signer = new ethers.Wallet(this.getPrivateKey(),provider);
        balance = await provider.getBalance(signer.address).toString();
        break;
      case "arbitrum":
        break;
      default:
        throw new Error('Invalid network');
        break;
    }
    return balance;
  }

  // 在比特币测试网和以太坊测试网发送交易
  public async sendTransaction(toAddress: string, amount: number) {
    if (this.network === 'bitcoin_testnet') {
      // 发送比特币测试网交易
      const payment = this.createPayment();
      const inputData = await this.getInputData(amount, payment);
      const outputData = {
        address: toAddress,
        value: amount * 8 / 10,
      }
      const psbt = new bitcoin.Psbt({ network: bitcoin.networks.testnet })
        .addInput(inputData)
        .addOutput(outputData)
        .signInput(0, this.childNode);
      psbt.finalizeAllInputs();
      const tx = psbt.extractTransaction();
      await regtestUtils.broadcast(tx.toHex());
    } else if (this.network === 'polygon') {
      const provider = ethers.getDefaultProvider(POLYGON_RPC_URL);
      // 发送以太坊交易
      const signer = new ethers.Wallet(this.getPrivateKey(),provider);
      await signer.sendTransaction({
        to: toAddress,
        value: parseEther(amount.toString())
      });
    } else {
      throw new Error('Invalid network');
    }
  }
}
